package workflow

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"path/filepath"

	"gopkg.in/yaml.v3"
)

// Definition represents a workflow definition
type Definition struct {
	// ID is the workflow ID
	ID string `json:"id" yaml:"id"`
	// Version is the workflow definition version
	Version string `json:"version" yaml:"version"`
	// Name is the workflow name
	Name string `json:"name" yaml:"name"`
	// Description is the workflow description
	Description string `json:"description" yaml:"description"`
	// Config is the workflow configuration
	Config map[string]interface{} `json:"config" yaml:"config"`
	// Sources are the workflow sources
	Sources map[string]SourceDefinition `json:"sources" yaml:"sources"`
	// Processors are the workflow processors
	Processors map[string]ProcessorDefinition `json:"processors" yaml:"processors"`
	// Sinks are the workflow sinks
	Sinks map[string]SinkDefinition `json:"sinks" yaml:"sinks"`
	// Edges are the workflow edges
	Edges []EdgeDefinition `json:"edges" yaml:"edges"`
}

// SourceDefinition represents a source definition
type SourceDefinition struct {
	// Type is the source type
	Type string `json:"type" yaml:"type"`
	// Config is the source configuration
	Config map[string]interface{} `json:"config" yaml:"config"`
}

// ProcessorDefinition represents a processor definition
type ProcessorDefinition struct {
	// Type is the processor type
	Type string `json:"type" yaml:"type"`
	// Config is the processor configuration
	Config map[string]interface{} `json:"config" yaml:"config"`
}

// SinkDefinition represents a sink definition
type SinkDefinition struct {
	// Type is the sink type
	Type string `json:"type" yaml:"type"`
	// Config is the sink configuration
	Config map[string]interface{} `json:"config" yaml:"config"`
}

// EdgeDefinition represents an edge definition
type EdgeDefinition struct {
	// From is the source node ID
	From string `json:"from" yaml:"from"`
	// To is the target node ID
	To string `json:"to" yaml:"to"`
}

// ParseFromFile parses a workflow definition from a file
func ParseFromFile(path string) (*Definition, error) {
	// Read file
	data, err := ioutil.ReadFile(path)
	if err != nil {
		return nil, fmt.Errorf("failed to read file: %w", err)
	}

	// Determine format based on file extension
	ext := filepath.Ext(path)
	var definition Definition

	switch ext {
	case ".json":
		// Parse JSON
		err = json.Unmarshal(data, &definition)
		if err != nil {
			return nil, fmt.Errorf("failed to parse JSON: %w", err)
		}
	case ".yaml", ".yml":
		// Parse YAML
		err = yaml.Unmarshal(data, &definition)
		if err != nil {
			return nil, fmt.Errorf("failed to parse YAML: %w", err)
		}
	default:
		return nil, fmt.Errorf("unsupported file format: %s", ext)
	}

	// Validate definition
	err = definition.Validate()
	if err != nil {
		return nil, fmt.Errorf("invalid workflow definition: %w", err)
	}

	return &definition, nil
}

// ParseFromString parses a workflow definition from a string
func ParseFromString(data string, format string) (*Definition, error) {
	var definition Definition

	switch format {
	case "json":
		// Parse JSON
		err := json.Unmarshal([]byte(data), &definition)
		if err != nil {
			return nil, fmt.Errorf("failed to parse JSON: %w", err)
		}
	case "yaml", "yml":
		// Parse YAML
		err := yaml.Unmarshal([]byte(data), &definition)
		if err != nil {
			return nil, fmt.Errorf("failed to parse YAML: %w", err)
		}
	default:
		return nil, fmt.Errorf("unsupported format: %s", format)
	}

	// Validate definition
	err := definition.Validate()
	if err != nil {
		return nil, fmt.Errorf("invalid workflow definition: %w", err)
	}

	return &definition, nil
}

// SaveToFile saves a workflow definition to a file
func (d *Definition) SaveToFile(path string) error {
	// Determine format based on file extension
	ext := filepath.Ext(path)
	var data []byte
	var err error

	switch ext {
	case ".json":
		// Marshal to JSON
		data, err = json.MarshalIndent(d, "", "  ")
		if err != nil {
			return fmt.Errorf("failed to marshal to JSON: %w", err)
		}
	case ".yaml", ".yml":
		// Marshal to YAML
		data, err = yaml.Marshal(d)
		if err != nil {
			return fmt.Errorf("failed to marshal to YAML: %w", err)
		}
	default:
		return fmt.Errorf("unsupported file format: %s", ext)
	}

	// Write to file
	err = ioutil.WriteFile(path, data, 0644)
	if err != nil {
		return fmt.Errorf("failed to write file: %w", err)
	}

	return nil
}

// Validate validates the workflow definition
func (d *Definition) Validate() error {
	// Check required fields
	if d.Version == "" {
		return fmt.Errorf("version is required")
	}
	if d.Name == "" {
		return fmt.Errorf("name is required")
	}

	// Check sources
	for id, source := range d.Sources {
		if source.Type == "" {
			return fmt.Errorf("source %s: type is required", id)
		}
	}

	// Check processors
	for id, processor := range d.Processors {
		if processor.Type == "" {
			return fmt.Errorf("processor %s: type is required", id)
		}
	}

	// Check sinks
	for id, sink := range d.Sinks {
		if sink.Type == "" {
			return fmt.Errorf("sink %s: type is required", id)
		}
	}

	// Check edges
	for i, edge := range d.Edges {
		if edge.From == "" {
			return fmt.Errorf("edge %d: from is required", i)
		}
		if edge.To == "" {
			return fmt.Errorf("edge %d: to is required", i)
		}

		// Check if source node exists
		if !d.nodeExists(edge.From) {
			return fmt.Errorf("edge %d: source node %s not found", i, edge.From)
		}

		// Check if target node exists
		if !d.nodeExists(edge.To) {
			return fmt.Errorf("edge %d: target node %s not found", i, edge.To)
		}
	}

	return nil
}

// nodeExists checks if a node exists
func (d *Definition) nodeExists(id string) bool {
	// Check sources
	if _, ok := d.Sources[id]; ok {
		return true
	}

	// Check processors
	if _, ok := d.Processors[id]; ok {
		return true
	}

	// Check sinks
	if _, ok := d.Sinks[id]; ok {
		return true
	}

	return false
}

// ToJSON converts the Definition to JSON
func (d *Definition) ToJSON() ([]byte, error) {
	return json.Marshal(d)
}

// FromJSON converts JSON to a Definition
func (d *Definition) FromJSON(data []byte) error {
	return json.Unmarshal(data, d)
}
